/*	$Id: session.c 1123 2010-12-16 04:03:27Z phrakt $	*/
/*
 * Copyright (c) 2008 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include <sys/param.h>
#include <sys/time.h>

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include <canopy/log.h>
#include <canopy/mem.h>
#include <canopy/htab.h>
#include <canopy/thread.h>
#include <canopy/string.h>

#include "http.h"
#include "session.h"
#include "private.h"


/*
 * http_session_create()
 *
 * Create a new HTTP session, initialize its contents, and return a pointer
 * to the new session's structure.
 * Returns a pointer to the new session on success, or NULL on failure.
 */
http_session_t*
http_session_create(http_sock_t *sock)
{
	http_session_t *sess;

	if ((sess = cnp_malloc(sizeof(*sess))) == NULL) {
		http_log_err("failed to allocate HTTP session");
		return (NULL);
	}
	memset(sess, 0, sizeof(*sess));

	if (cnp_mutex_init(&sess->hs_lock, NULL) == -1) {
		http_log_err("failed to initialize session lock");
		http_session_free(sess);
		return (NULL);
	}

	sess->hs_attr = cnp_htab_alloc(8, CNP_HTAB_COPYKEY, NULL, NULL, NULL);
	if (sess->hs_attr == NULL) {
		http_session_free(sess);
		return (NULL);
	}

	sess->hs_refcnt = 1;
	sess->hs_sock = sock;
	sess->hs_ctime = time(&sess->hs_ctime);
	sess->hs_atime = sess->hs_ctime;

	return (sess);
}

/*
 * http_session_close()
 *
 */
void
http_session_close(http_session_t *sess)
{
	if (cnp_mutex_lock(&sess->hs_lock) == -1) {
		http_log_err("failed to lock session");
		return;
	}


	(void)cnp_mutex_unlock(&sess->hs_lock);
}

/*
 * http_session_free()
 *
 */
void
http_session_free(http_session_t *sess)
{
	int rc;

	if (cnp_mutex_lock(&sess->hs_lock) == -1) {
		http_log_err("failed to lock session");
		return;
	}

	sess->hs_refcnt--;
	rc = sess->hs_refcnt;

	(void)cnp_mutex_unlock(&sess->hs_lock);

	if (rc == 0) {
		(void)cnp_mutex_destroy(&sess->hs_lock);

		if (sess->hs_attr != NULL)
			cnp_htab_free(sess->hs_attr);
		if (sess->hs_user_agent != NULL)
			cnp_free(sess->hs_user_agent);

		cnp_free(sess);
	}
}

/*
 * http_session_setattr()
 *
 * Returns 0 on success, or -1 on failure.
 */
void*
http_session_getattr(http_session_t *sess, const char *key)
{
	void *value = NULL;

	if (cnp_mutex_lock(&sess->hs_lock) == -1) {
		http_log_err("failed to lock session");
		return (NULL);
	}

	value = cnp_htab_strlookup(sess->hs_attr, key);

	(void)cnp_mutex_unlock(&sess->hs_lock);

	return (value);
}

/*
 * http_session_setattr()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_session_setattr(http_session_t *sess, const char *key, void *value)
{
	int ret;

	if (cnp_mutex_lock(&sess->hs_lock) == -1) {
		http_log_err("failed to lock session");
		return (-1);
	}

	ret = cnp_htab_strinsert(sess->hs_attr, key, value);

	(void)cnp_mutex_unlock(&sess->hs_lock);

	return (ret);
}

/*
 * http_session_getflag()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_session_getflag(http_session_t *sess, int iflags, int *oflags)
{
	if (cnp_mutex_lock(&sess->hs_lock) == -1) {
		http_log_err("failed to lock session");
		return (-1);
	}

	*oflags = (sess->hs_flags & iflags);

	(void)cnp_mutex_unlock(&sess->hs_lock);

	return (0);
}

/*
 * http_session_setflag()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_session_setflag(http_session_t *sess, int flags)
{
	if (cnp_mutex_lock(&sess->hs_lock) == -1) {
		http_log_err("failed to lock session");
		return (-1);
	}

	sess->hs_flags |= flags;

	(void)cnp_mutex_unlock(&sess->hs_lock);

	return (0);
}


/*
 * http_session_getsock()
 *
 * Returns a reference to the HTTP socket associated with the session <sess>.
 */
http_sock_t*
http_session_getsock(const http_session_t *sess)
{
	return (sess->hs_sock);
}

/*
 * http_session_isexpired()
 *
 * Check if the session <sess> is expired and return 1 if it is, or 0 if
 * it's not.  A session is considered expired if the difference between the
 * current time and the last access time is equal to or greater than the
 * session's timeout value.
 */
int
http_session_isexpired(const http_session_t *sess)
{
	struct timeval tv;

	(void)gettimeofday(&tv, NULL);

	if ((tv.tv_sec - sess->hs_atime) >= sess->hs_timeout)
		return (1);

	return (0);
}

/*
 * http_session_setagent()
 *
 */
int
http_session_setagent(http_session_t *sess, const char *user_agent)
{
	char *tmp;

	http_log_debug("setting User-Agent to `%s' for session %u",
	    user_agent, sess->hs_id);

	if ((tmp = cnp_strdup(user_agent)) == NULL) {
		http_log_err("failed to allocate User-Agent string");
		return (-1);
	}

	if (cnp_mutex_lock(&sess->hs_lock) == -1) {
		http_log_err("failed to lock session");
		cnp_free(tmp);
		return (-1);
	}

	if (sess->hs_user_agent != NULL)
		cnp_free(sess->hs_user_agent);

	sess->hs_user_agent = tmp;
	sess->hs_atime = time(&sess->hs_atime);

	(void)cnp_mutex_unlock(&sess->hs_lock);
	return (0);
}

/*
 * http_session_settimeout()
 *
 */
int
http_session_settimeout(http_session_t *sess, time_t timeout)
{
	if (cnp_mutex_lock(&sess->hs_lock) == -1) {
		http_log_err("failed to lock session");
		return (-1);
	}

	http_log_debug("setting timeout to %d seconds for session %u",
	    timeout, sess->hs_id);

	sess->hs_timeout = timeout;

	(void)cnp_mutex_unlock(&sess->hs_lock);
	return (0);
}
